<?php
/*
 *gitLab的web钩子程序
 *author：zhuqi
 *zhu.cheer@qq.com
 *
 *建议请不要将此程序运用在生产环境，您可以在充分了解了本程序的运行原理后，并对安全性做了充分考量后再做打算
 *
 *参数范例：
 *支持多个git项目
 *array(
		'project'=>'project_name',//git项目名称
		'token'=>'g14sQCQWmgXYZzV7',//gitlab上配置的token,用来校验
		'branch_sync'=>'master',//当哪个分支push时触发
		'from_path'=>'/home/www/code/project_name/',//代码源，就是你本地修改需要发布到其他服务器的目录；
		'rsync_passwd'=>'/home/www/rsync.pas',//配置的rsync密码路径
		'to_path'=>array(
			//如果是同步到其他服务器需要配置rsync,可以找运维协助，或自己研究，目前rsync采用的服务端模式
			'rsync://user@192.168.xx.xx/git-project',//git-project是把代码同步上去的那台机器的rsync模块配置名称
			'/home/www/tobackup/'
		)
	),
 *
 */
$projectOption = array(
	array(
		'project'=>'project_name',
		'token'=>'g14sQCQWmgXYZzV7',
		'branch_sync'=>'master',
		'from_path'=>'/home/www/code/project_name/',
		'rsync_passwd'=>'/home/www/rsync.pas',
		'to_path'=>array(
			'rsync://user@192.168.xx.xx/project_name',
		)
	),	
);

//日志路径
$log_path = '/home/logs/git_hooker.log';

//什么操作触发 push/tag_push/issue/note/merge_request
$allow_action = array('push');

//允许谁操作
$allow_user = array(
	'admin@admin.com',
);



$hooker = new HookJob($project_option, $log_path, $allow_action, $allow_user);
$hooker->runSync();;




class HookJob{
	
	private $option;
	private $log_path = '/home/logs/git_hooker.log';
	private $allow_action = array('push');
	private $allow_user = '*';
	public function __construct($project_option, $log_path='', $allow_action=array('push'), $allow_user='*'){
		$this->option = array();
		foreach($project_option as $item){
			$this->option[$item['project'].'#'.$item['branch_sync']] = $item;
		}
		if(!empty($log_path)){
			$this->log_path = $log_path;
		}
		if(!empty($allow_action)){
			$this->allow_action = $allow_action;
		}
		if(!empty($allow_user)){
			$this->allow_user = $allow_user;
		}
		
		if (function_exists('fastcgi_finish_request')) {
            fastcgi_finish_request();
        }
	}
	
	public function runSync(){
		$projectInfo = $this->getProjectInfo();
		
		foreach($projectInfo['to_path'] as $item){
			if(false !== strpos($item, 'rsync://')){
				$shellCommand = "/usr/bin/rsync -avz --delete --exclude=.git  --password-file={$projectInfo['rsync_passwd']} {$projectInfo['from_path']} {$item} --port=873";
			}else{
				$shellCommand = "/usr/bin/rsync -avz --delete --exclude=.git  {$projectInfo['from_path']} {$item} --port=873"; 
				
			}
			$log = '['.date('Y-m-d H:i:s').'] 执行命名:'.$shellCommand."\r\n";
		}
		file_put_contents($this->log_path, $log, FILE_APPEND );
		$exec = `$shellCommand`;
		if(empty($exec)){
			$this->throwError('命令执行出现问题,请检查php.ini将shell_exec函数取消禁用！');
		}
		return $exec;
	}
	
	private function getHookInfo(){
		//也可以安装pecl_http扩展来获取请求头信息
		$httpBody = json_decode(file_get_contents('php://input'),true);
		$token = empty($_SERVER['HTTP_X_GITLAB_TOKEN']) ? '' : $_SERVER['HTTP_X_GITLAB_TOKEN'];
		$hookInfo['project'] = empty($httpBody['project']['name']) ? '' : $httpBody['project']['name'];
		$hookInfo['action'] = empty($httpBody['object_kind']) ? '' : $httpBody['object_kind'];
		$hookInfo['token'] = $token;
		$hookInfo['user'] = empty($httpBody['user_email']) ? '' : $httpBody['user_email'];
		$hookInfo['branch_sync'] = empty($httpBody['ref']) ? '' : str_replace('refs/heads/', '', $httpBody['ref']);

		return $hookInfo;
	}
	
	private function getProjectInfo(){
		$hookInfo = $this->getHookInfo();
		if(!in_array($hookInfo['action'], $this->allow_action)){
			$this->throwError(var_export('此操作被限制', true));
		}
		if($this->allow_user != '*' && !in_array($hookInfo['user'], $this->allow_user)){
			return $this->throwError('该用户不被允许操作');
		}
		if(empty($this->option[$hookInfo['project'].'#'.$hookInfo['branch_sync']])){
			return $this->throwError('未找到同步配置');
		}
		if($this->option[$hookInfo['project'].'#'.$hookInfo['branch_sync']]['token'] != $hookInfo['token']){
			return $this->throwError('同步Token校验失败');
		}
		$projectInfo = $this->option[$hookInfo['project'].'#'.$hookInfo['branch_sync']];
		
		return $projectInfo;
	}
	
	private function throwError($message){
		header("HTTP/1.1 404 Not Found"); 
		$log = '['.date('Y-m-d H:i:s').'] 执行报错:'.$message."\r\n";
		file_put_contents($this->log_path, $log, FILE_APPEND );
		exit($message);
	}
	
}
